#include <stdio.h>
#include <unistd.h> 
#include <string.h>
#include <sys/types.h> 
#include <sys/wait.h>
#include <assert.h> 
#include <stdlib.h> 
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <ctype.h>
char commondLine[1024];
char* myargv[64];
int sig = 0;
int code = 0;
#define NULL_REDIR 0
#define CINPUT_REDIR 1
#define OUTPUT_REDIR 2
#define APPEND_REDIR 3
int redirType = 0;
char* redirFile = NULL;
//去除空格
#define trimSpace(start) do {\
    while(isspace(*start)) ++start;\
}while(0)
void commondCheck(char* commonds)
{
    assert(commonds);
    char* start = commonds;
    char* end = commonds + strlen(commonds);
    while(start < end)
    {
        if('<' == *start)
        {
            *start = 0;
            ++start;
            trimSpace(start);
            redirType = CINPUT_REDIR;
            redirFile = start;
            break;
        }
        else if('>' == *start)
        {
            *start = 0;
            ++start;
            //"ls -a -l" > filename 
            if('>' == *start)
            {
                *start = 0;
                ++start;
                redirType = APPEND_REDIR;
            }
            //"ls -a -l" >> filename
            else 
            {
                redirType = OUTPUT_REDIR;
            }
            trimSpace(start);
            redirFile = start;
            break;

        }
        else 
        {
            start++;
        }
        
    }
}
int main()
{
    while(1)
    {
        redirType = NULL_REDIR;
        redirFile = NULL;
        int status;
        printf("[用户名@主机名 文件名]$ ");
        fflush(stdout);
        char* str = fgets(commondLine, 1024, stdin); //-1是保证\0被读入
        assert(str != NULL);
        (void)str;
        //把读入的\n替换成\0
        commondLine[strlen(commondLine) - 1] = 0;
        //"ls -l -a" ->  "ls -a -l"
        //"ls -a -l > file" -> "ls -a -l" > "file"
        commondCheck(commondLine);
        //切割字符串，存入一个指针数组中
        myargv[0] = strtok(commondLine, " ");
        int i = 1;
        //当命令未ls时，自动添加带颜色选项
        if(myargv[0] != NULL && strcmp(myargv[0], "ls") == 0)
        {
            myargv[i++] = (char*)"--color=auto";
        }
        while(myargv[i++] = strtok(NULL, " "));
        //内建/内置命令处理
        if(myargv[0] != NULL && myargv[1] != NULL && strcmp(myargv[0], "cd") == 0)
        {
            chdir(myargv[1]);
            continue;
        }
        if(myargv[0] != NULL && myargv[1] != NULL && strcmp(myargv[0], "echo") == 0)
        {
            if(strcmp(myargv[1], "$?") == 0)
            {
                printf("%d %d\n", code , sig);
            }
            else 
            {
                printf("%s\n", myargv[1]);
            }
            continue;
        }
        

        //如果没有子串了，strtok返回NULL，此时myargv[end] = NULL
//条件编译检查是否截断字符串成功
#ifdef DEBUG 
    for(int j = 0; myargv[j]; ++j)
    {
        printf("argv[%d]: %s\n", j, myargv[j]);
    }
#endif
        pid_t id = fork();
        assert(id != -1);
        if(id == 0)
        {
            //因为命令是子进程执行的，正在的重定向工作任务一定是子进程完成的
            //如何重定向，是父进程要给子进程提供信息的
            //重定向不影响父进程-》进程具有独立性
            switch(redirType)
            {
                case CINPUT_REDIR:
                {
                    int fd = open(redirFile, O_RDONLY);
                    if(fd < 0)
                    {
                        perror("open");
                        exit(errno);
                    }
                    dup2(fd, 0);
                    close(fd);
                }
                    break;
                case OUTPUT_REDIR:
                case APPEND_REDIR:
                {        
                        int flag = O_WRONLY | O_CREAT;
                        if(redirType == OUTPUT_REDIR) flag |= O_TRUNC;
                        else flag |= O_APPEND;
                        int fd = open(redirFile, flag, 0666);
                        if(fd < 0)
                        {
                            perror("open");
                            exit(errno);
                        }
                        dup2(fd, 1);
                        close(fd); 
                    break;
                }
                case NULL_REDIR:
                    break;
                default:
                    printf("error?\n");
                    break;
            }
            execvp(myargv[0], myargv);//程序替换的时候，会不会影响曾经打开的文件？
                                     //不会，因为替换的是代码和数据，而文件属性是属于系统内核数据结构
            exit(1);
        }
        pid_t ret = waitpid(id, &status, 0);
        assert(ret > 0);
        (void)ret;
        sig = status & 0x7f;
        code = status >> 8 & 0xff;
    }
}

